﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using LitJson;
using System.Globalization;
using System.Web;
using System.Diagnostics;

namespace SynoManager.DownloadStation
{
    /// <summary>
    /// Represents a task on the Download Station
    /// </summary>
    /// <remarks>New tasks can be created using the <see cref="DownloadStationService.Add(string)" /> method. 
    public class DownloadTask
    {
        #region Fields

        internal readonly DownloadStationService service;

        #endregion

        #region Task Properties

        #region Fields

        private int task_id;
        private string username;
        private int pid;
        private string url;
        private string filename;
        private int status;
        private int created_time;
        private int started_time;
        private string total_size;
        private string current_size;
        private int current_rate;
        private string extra_info;
        private int total_peers;
        private string progress;
        private int connected_peers;
        private int total_pieces;
        private int downloaded_pieces;
        private int available_pieces;
        private int upload_rate;
        private long total_upload;
        private int seeding_ratio;
        private int seeding_interval;

        private const short averageSampleSize = 5;
        private RingBuffer<int> currentRateAvg = new RingBuffer<int>(averageSampleSize);
        private RingBuffer<int> uploadRateAvg = new RingBuffer<int>(averageSampleSize);

        #endregion

        #region Properties

        public int TaskID
        {
            get { return task_id; }
        }

        public string Username
        {
            get { return username; }
        }

        public int Pid
        {
            get { return pid; }
        }

        public string Url
        {
            get { return url; }
        }

        public string Filename
        {
            get { return filename; }
        }

        public DownloadState State
        {
            get { return (DownloadState) status; }
        }

        public DateTime TimeCreated
        {
            get { return new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(created_time); }
        }

        public DateTime TimeStarted
        {
            get { return new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(started_time); }
        }

        public ByteSize SizeTotal
        {
            get { return new ByteSize(total_size); }
        }

        public ByteSize SizeCurrent
        {
            get { return new ByteSize(current_size); }
        }

        public int RateCurrent
        {
            get
            {
                if ((State == DownloadState.Downloading) || (State == DownloadState.Seeding))
                    return current_rate;
                else
                    return 0;
            }
        }

        public int RateUpload
        {
            get
            {
                if ((State == DownloadState.Downloading) || (State == DownloadState.Seeding))
                    return upload_rate;
                else
                    return 0;
            }
        }

        public string ExtraInfo
        {
            get { return extra_info; }
        }

        public int PeersTotal
        {
            get { return total_peers; }
        }

        public int PeersConnected
        {
            get { return connected_peers; }
        }

        public int PiecesTotal
        {
            get { return total_pieces; }
        }

        public int PiecesDownloaded
        {
            get { return downloaded_pieces; }
        }

        public int PiecesAvailable
        {
            get { return available_pieces; }
        }

        public long UploadTotal
        {
            get { return total_upload; }
        }

        public int SeedingRatio
        {
            get { return seeding_ratio; }
        }

        public int SeedingInterval
        {
            get { return seeding_interval; }
        }

        public TimeSpan RemainingEstimate
        {
            get
            {
                if ((current_size == "NA") || (total_size == "NA"))
                    return new TimeSpan(0);

                long bytesDown = ByteSize.Parse(current_size);
                long bytesTotal = ByteSize.Parse(total_size);

                long bytesRemaining = bytesTotal - bytesDown;
                var rateAverage = RateAverage;
                
                if (rateAverage == 0 || bytesRemaining == 0)
                    return new TimeSpan(0);

                int secondsRemaining = (int)(bytesRemaining / rateAverage);
                return new TimeSpan(0, 0, secondsRemaining);
            }
        }

        public decimal Progress
        {
            get
            {
                if (String.IsNullOrEmpty(progress) || (progress == "NA"))
                    return 0;

                return decimal.Parse(progress.TrimEnd('%'), CultureInfo.InvariantCulture) / 100;
            }
        }

        public long RateAverage
        {
            get
            {
                return (long) currentRateAvg.Average();
            }
        }

        public long UploadRateAverage
        {
             get
             {
                 return (long)uploadRateAvg.Average();
             }
        }

        #endregion

        #endregion

        #region Constructors

        internal DownloadTask(DownloadStationService service)
        {
            this.service = service;
        }

        internal DownloadTask(DownloadStationService service, DownloadRedirector.DownloadTaskInfo data)
        {
            this.service = service;

            // Load all fields from the datareader
            // I could do this by reflection, but I think the performance hit is to big
            task_id = data.id;
            username = data.username;
            pid = data.pid;
            url = data.url;
            filename = data.filename;
            status = data.status;
            created_time = data.createdTime;
            started_time = data.startedTime;
            total_size = data.totalSize;
            current_size = data.currentSize;
            current_rate = data.currentRate;
            extra_info =data.extraInfo;
            total_peers = data.totalPeers;
            progress = data.progress;
            connected_peers = data.connectedPeers;
            total_pieces = data.totalPieces;
            downloaded_pieces = data.downloadedPieces;
            available_pieces = data.availablePieces;
            upload_rate = data.uploadRate;
            total_upload = data.totalUpload;
            seeding_ratio = data.seedingRatio;
            seeding_interval = data.seedingInterval;

            // create sample array for average speed;
            for (int i = 0; i < averageSampleSize; i++)
            {
                currentRateAvg.Add(current_rate);
                uploadRateAvg.Add(upload_rate);
            }
        }

        #endregion

        internal bool Update(DownloadRedirector.DownloadTaskInfo data)
        {
            bool updated = false;

            if (task_id != data.id)
            {
                task_id = data.id;
                updated = true;
            }

            if (username != data.username)
            {
                username = data.username;
                updated = true;
            }

            if (pid != data.pid)
            {
                pid = data.pid;
                updated = true;
            }

            if (url != data.url)
            {
                url = data.url;
                updated = true;
            }

            if (filename != data.filename)
            {
                filename = data.filename;
                updated = true;
            }

            if (status != data.status)
            {
                status = data.status;
                Debug.WriteLine("Task " + task_id + " changed to " + State, "DownloadTask");
                OnStatusChanged(State);
                updated = true;
            }

            if (created_time != data.createdTime)
            {
                created_time = data.createdTime;
                updated = true;
            }

            if (started_time != data.startedTime)
            {
                started_time = data.startedTime;
                updated = true;
            }

            if (total_size != data.totalSize)
            {
                total_size = data.totalSize;
                updated = true;
            }

            if (current_size != data.currentSize)
            {
                current_size = data.currentSize;
                updated = true;
            }

            if (current_rate != data.currentRate)
            {
                current_rate = data.currentRate;
                updated = true;
            }

            if (extra_info != data.extraInfo)
            {
                extra_info = data.extraInfo;
                updated = true;
            }

            if (total_peers != data.totalPeers)
            {
                total_peers = data.totalPeers;
                updated = true;
            }

            if (progress != data.progress)
            {
                progress = data.progress;
                updated = true;
            }

            if (connected_peers != data.connectedPeers)
            {
                connected_peers = data.connectedPeers;
                updated = true;
            }

            if (total_pieces != data.totalPieces)
            {
                total_pieces = data.totalPieces;
                updated = true;
            }

            if (downloaded_pieces != data.downloadedPieces)
            {
                downloaded_pieces = data.downloadedPieces;
                updated = true;
            }

            if (available_pieces != data.availablePieces)
            {
                available_pieces = data.availablePieces;
                updated = true;
            }

            if (upload_rate != data.uploadRate)
            {
                upload_rate = data.uploadRate;
                updated = true;
            }

            if (total_upload != data.totalUpload)
            {
                total_upload = data.totalUpload;
                updated = true;
            }

            if (seeding_ratio != data.seedingRatio)
            {
                seeding_ratio = data.seedingRatio;
                updated = true;
            }

            if (seeding_interval != data.seedingInterval)
            {
                seeding_interval = data.seedingInterval;
                updated = true;
            }

            // update average:
            currentRateAvg.Add(current_rate);
            uploadRateAvg.Add(upload_rate);

            if (updated)
                OnUpdated();

            return updated;
        }

        #region Events

        public event DownloadTaskStatusEventHandler StatusChanged;
        public event DownloadTaskEventHandler Updated;

        protected void OnStatusChanged(DownloadState newState)
        {
            if (StatusChanged != null)
            {
                Delegate[] delegates = StatusChanged.GetInvocationList();
                foreach (Delegate del in delegates)
                {
                    ISynchronizeInvoke target = del.Target as ISynchronizeInvoke;

                    if (target != null && target.InvokeRequired)
                    {
                        // eventhandler moet op een andere thread uitgevoerd worden
                        target.Invoke(del, new object[] {this, new DownloadTaskStatusEventArgs(this, newState)});
                    }
                    else
                    {
                        del.DynamicInvoke(new object[] {this, new DownloadTaskStatusEventArgs(this, newState)});
                    }
                }
            }
        }

        protected void OnUpdated()
        {
            if (Updated != null)
            {
                Delegate[] delegates = Updated.GetInvocationList();
                foreach (Delegate del in delegates)
                {
                    ISynchronizeInvoke target = del.Target as ISynchronizeInvoke;

                    if (target != null && target.InvokeRequired)
                    {
                        // eventhandler moet op een andere thread uitgevoerd worden
                        target.Invoke(del, new object[] {this, new DownloadTaskEventArgs(this)});
                    }
                    else
                    {
                        del.DynamicInvoke(new object[] {this, new DownloadTaskEventArgs(this)});
                    }
                }
            }
        }

        #endregion

        #region State Functions

        public void Pause()
        {
            service.redirector.Stop(task_id);
        }

        public void Resume()
        {
            service.redirector.Resume(task_id);
        }

        public void Remove()
        {
            service.redirector.Delete(task_id);
        }

        #endregion

        /// <summary>
        /// Gets the location to where the file is downloaded
        /// </summary>
        public string DownloadLocation
        {
            get { return Path.Combine(service.DownloadFolder, filename); }
        }

        internal void OpenFile()
        {
            string location = DownloadLocation;
            if (File.Exists(location) || Directory.Exists(location))
            {
                Process.Start(location);
            }
            else
            {
                throw new FileNotFoundException("Could not open file. Is the share accessible?", location);
            }
        }

        #region Files
        public DownloadTaskFileCollection Files
        {
            get
            {
                return new DownloadTaskFileCollection(this);
            }
        }
        #endregion

        #region Advanced Properties
        private DownloadStationClient.GetOneData fullData;
        internal DownloadStationClient.GetOneData _FullData
        {
            get
            {
                if (fullData == null)
                    fullData = service.client.GetOne(task_id).data;
                return fullData;
            }
        }

        public DownloadTaskType TaskType
        {
            get
            {
                var fd = _FullData;
                if (fd.isnzb)
                    return DownloadTaskType.Nzb;
                else if (fd.istorrent)
                    return DownloadTaskType.BitTorrent;
                else if (fd.url.StartsWith("http"))
                    return DownloadTaskType.Http;
                else if (fd.url.StartsWith("ftp"))
                    return DownloadTaskType.Ftp;
                else
                    return DownloadTaskType.Unknown;
            }
        }
        #endregion
    }
}